//
//  AVCamPhotoCaptureDelegate.m
//  CaptureDemo
//
//  Created by 苏沫离 on 2018/9/19.
//  Copyright © 2018年 苏沫离. All rights reserved.
//

#import "AVCamPhotoCaptureDelegate.h"
#import <Photos/Photos.h>

@interface AVCamPhotoCaptureDelegate ()

//拍摄照片的一些设置
@property (nonatomic, readwrite) AVCapturePhotoSettings *requestedPhotoSettings;

//即将拍摄照片的回调
@property (nonatomic) void (^willCapturePhotoAnimation)(void);

//开始拍摄动态照片，回调为YES ；结束拍摄动态照片，回调为 NO
@property (nonatomic) void (^livePhotoCaptureHandler)(BOOL capturing);

//完成拍摄的回调
@property (nonatomic) void (^completionHandler)(AVCamPhotoCaptureDelegate *photoCaptureDelegate);

@property (nonatomic) NSData *photoData;
@property (nonatomic) NSURL *livePhotoCompanionMovieURL;//动态照片存储的文件地址

@end

@implementation AVCamPhotoCaptureDelegate

- (instancetype)initWithRequestedPhotoSettings:(AVCapturePhotoSettings *)requestedPhotoSettings willCapturePhotoAnimation:(void (^)(void))willCapturePhotoAnimation livePhotoCaptureHandler:(void (^)(BOOL))livePhotoCaptureHandler completionHandler:(void (^)(AVCamPhotoCaptureDelegate *))completionHandler
{
    self = [super init];
    if ( self ) {
        self.requestedPhotoSettings = requestedPhotoSettings;
        self.willCapturePhotoAnimation = willCapturePhotoAnimation;
        self.livePhotoCaptureHandler = livePhotoCaptureHandler;
        self.completionHandler = completionHandler;
    }
    return self;
}

//拍摄照片完成之后，在清除已分配的与此拍摄请求相关的任何资源。
- (void)didFinish
{
    if ( [[NSFileManager defaultManager] fileExistsAtPath:self.livePhotoCompanionMovieURL.path] ) {
        NSError *error = nil;
        //拍摄照片完成之后，在清除已分配的与此拍摄请求相关的任何资源。
        [[NSFileManager defaultManager] removeItemAtPath:self.livePhotoCompanionMovieURL.path error:&error];
        
        if ( error ) {
            NSLog( @"Could not remove file at url: %@", self.livePhotoCompanionMovieURL.path );
        }
    }
    self.completionHandler(self);
}


#pragma mark - 拍摄 进度

/* 拍摄输出已解析设置
 * 
 * 在调用 - capturePhotoWithSettings:delegate: 方法之后，尽可能早地调用该协议方法
 * 此时，可以尽早找到 AVCapturePhotoOutput 为自动设置 AVCapturePhotoSettings 选择的值：如 flashEnabled 属性指示在拍摄期间是否会触发闪光灯
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput willBeginCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
    //动态照片内容的宽高；如果不请求拍摄动态照片则宽高为0，
    if ( (resolvedSettings.livePhotoMovieDimensions.width > 0) && (resolvedSettings.livePhotoMovieDimensions.height > 0)) {
        self.livePhotoCaptureHandler(YES);
    }
}

/* 即将拍摄照片
 *
 * AVCapturePhotoOutput 调用此方法尽可能接近拍摄的初始时刻。
 * 如果启用了快门声，则在AVCapturePhotoOutput开始播放快门声后立即调用此方法
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput willCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
    self.willCapturePhotoAnimation();
}


/* 已拍摄照片
 *
 * 一旦拍摄的第一步结束，即在摄影曝光时间结束时，AVCapturePhotoOutput就会调用此方法。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)output didCapturePhotoForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
    
}

/* 拍摄照片已完成
 *
 * 当整个拍摄过程完成时，AVCapturePhotoOutput会调用此方法，并且不会再为此拍摄请求发送任何协议消息。
 * 在这里清除已分配的与此拍摄请求相关的任何资源。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishCaptureForResolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings error:(NSError *)error
{
    if ( error != nil ) {
        NSLog( @"Error capturing photo: %@", error );
        [self didFinish];
        return;
    }
    
    if ( self.photoData == nil ) {
        NSLog( @"No photo data resource" );
        [self didFinish];
        return;
    }
    
    [PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
        if ( status == PHAuthorizationStatusAuthorized ) {
            [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
                PHAssetResourceCreationOptions *options = [[PHAssetResourceCreationOptions alloc] init];
                if (@available(iOS 11.0, *)) {
                    //资源的统一类型标识符。 如果未指定，将从PHAssetResourceType推断出一个。
                    options.uniformTypeIdentifier = self.requestedPhotoSettings.processedFileType;
                } else {
                    // Fallback on earlier versions
                }
                PHAssetCreationRequest *creationRequest = [PHAssetCreationRequest creationRequestForAsset];
                [creationRequest addResourceWithType:PHAssetResourceTypePhoto data:self.photoData options:options];
                
                if ( self.livePhotoCompanionMovieURL ) {
                    PHAssetResourceCreationOptions *livePhotoCompanionMovieResourceOptions = [[PHAssetResourceCreationOptions alloc] init];
                    livePhotoCompanionMovieResourceOptions.shouldMoveFile = YES;
                    [creationRequest addResourceWithType:PHAssetResourceTypePairedVideo fileURL:self.livePhotoCompanionMovieURL options:livePhotoCompanionMovieResourceOptions];
                }
            } completionHandler:^( BOOL success, NSError * _Nullable error ) {
                if ( ! success ) {
                    NSLog( @"Error occurred while saving photo to photo library: %@", error );
                }
                
                [self didFinish];
            }];
        }
        else {
            NSLog( @"Not authorized to save photo" );
            [self didFinish];
        }
    }];
}

#pragma mark - 接收拍摄结果


/* 提供拍摄的JPEG等格式图像
 *
 * 如果以JPEG等格式请求拍摄，则AVCapturePhotoOutput会对拍摄请求中的每次曝光调用此方法一次。如果拍摄单个图像，则会调用此方法一次。如果要求使用多次曝光进行包围拍摄，则每次曝光都会调用一次此方法。
 *
 * photoSampleBuffer 包含捕获的照片的样本缓冲区，可以是未压缩的像素缓冲区或压缩的图像数据，以及计时信息和其他元数据。
 * previewPhotoSampleBuffer 如果请求缩略图，则会显示一个样本缓冲区，其中包含所请求格式的缩略图照片。如果未请求预览传递，或者拍摄失败，则此参数为nil。
 * bracketSettings 如果请求使用AVCapturePhotoBracketSettings对多个图像进行括号捕获，则会在方括号内的静止图像设置对象中描述此委托调用对应的括号中的哪个图像。如果没有请求包围捕获，则此参数为nil。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)output
didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer
previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
     resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
      bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings
                error:(NSError *)error NS_DEPRECATED_IOS(10_0, 11_0)
{
    if ( error != nil ) {
        NSLog( @"Error capturing photo: %@", error );
        return;
    }
    
    CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(photoSampleBuffer);
    size_t length = CMBlockBufferGetDataLength(blockBufferRef);
    Byte buffer[length];
    CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);
    self.photoData = [NSData dataWithBytes:buffer length:length];
}


/* 提供RAW格式图像。
 * 如果请求拍摄RAW格式，则AVCapturePhotoOutput会在拍摄请求中为每次曝光调用此方法一次。如果拍摄单个图像，则会调用此方法一次。如果要求使用多次曝光进行包围拍摄，则每次曝光都会调用一次此方法。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)output
didFinishProcessingRawPhotoSampleBuffer:(CMSampleBufferRef)rawSampleBuffer
previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer
     resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
      bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings
                error:(NSError *)error NS_DEPRECATED_IOS(10_0, 11_0)
{
    if ( error != nil ) {
        NSLog( @"Error capturing photo: %@", error );
        return;
    }
    
    CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(rawSampleBuffer);
    size_t length = CMBlockBufferGetDataLength(blockBufferRef);
    Byte buffer[length];
    CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);
    self.photoData = [NSData dataWithBytes:buffer length:length];
}

/* 提供拍摄照片产生的相关元数据
 *
 * 对于所有静止图像拍摄工作，建议使用此方法
 * 如果请求深度数据传递，则需要使用此方法
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(nullable NSError *)error
API_AVAILABLE(ios(11.0)){
    if ( error != nil ) {
        NSLog( @"Error capturing photo: %@", error );
        return;
    }
    self.photoData = [photo fileDataRepresentation];
}

/* 动态照片已完成拍摄
 *
 * 拍摄动态照片时，AVCapturePhotoOutput会调用此方法；此时，动态照片内容尚未处理或写入存储。
 * 每次拍摄动态照片时，AVCapturePhotoOutput仅调用此方法一次。
 * 使用此方法确定何时更改显示的UI以指示不再进行动态照片拍摄：例如，当用户按下快门按钮时，应用程序会显示“LIVE”图标，然后在拍摄动画结束时隐藏该图标。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishRecordingLivePhotoMovieForEventualFileAtURL:(NSURL *)outputFileURL resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings
{
    self.livePhotoCaptureHandler(NO);
}

/* 动态照片拍摄结果
 *
 * 调用该方法可以接收动态照片的拍摄结果；如果不拍摄动态照片，则无需实现此方法。
 * 每次拍摄动态照片时，AVCapturePhotoOutput仅调用此方法一次。
 *
 * 要将拍摄的动态照片添加到用户的照片库，需要使用PHAssetCreationRequest类。
 * 要使用照片库中的动态照片，需要使用PHLivePhoto和PHLivePhotoView类。
 * 要在 Web 上显示动态照片内容，需要使用LivePhotosKit JS框架。
 *
 * 参数：
 * 参数outputFileURL：写入动态照片内容的fileURL
 * 参数duration：动态照片的持续时间
 * 参数photoDisplayTime：动态照片的静止图像部分所对应的时间戳。
 */
- (void)captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingLivePhotoToMovieFileAtURL:(NSURL *)outputFileURL duration:(CMTime)duration photoDisplayTime:(CMTime)photoDisplayTime resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings error:(NSError *)error
{
    if ( error != nil ) {
        NSLog( @"Error processing live photo companion movie: %@", error );
        return;
    }
    self.livePhotoCompanionMovieURL = outputFileURL;
}

@end
